use std::{
    fs::{self, File},
    io::{self, Read, Write},
    path,
};

use crate::{mbc::interface::get_mbc, StrResult};

use super::interface::IMbc;

pub(crate) struct FileBackedMBC {
    rampath: path::PathBuf,
    mbc: Box<dyn IMbc>,
}

impl FileBackedMBC {
    pub fn new(rom_path: path::PathBuf, skip_checksum: bool) -> StrResult<FileBackedMBC> {
        let mut data = vec![];

        File::open(&rom_path)
            .and_then(|mut f| f.read_to_end(&mut data))
            .map_err(|_| "Could not read rom file")?;

        let rampath = rom_path.with_extension("gbsave");

        let mut mbc = get_mbc(data, skip_checksum)?;

        if mbc.is_battery_backed() {
            match File::open(&rampath) {
                Ok(mut file) => {
                    let mut ramdata: Vec<u8> = vec![];
                    match file.read_to_end(&mut ramdata) {
                        Err(..) => return Err("Error while reading existing save file"),
                        Ok(..) => {
                            mbc.loadram(&ramdata)?;
                        }
                    }
                }
                Err(ref e) if e.kind() == io::ErrorKind::NotFound => {}
                Err(_) => return Err("Error loading existing save file"),
            }
        }

        Ok(FileBackedMBC { rampath, mbc })
    }
}

impl IMbc for FileBackedMBC {
    fn readram(&self, addr: u16) -> u8 {
        self.mbc.readram(addr)
    }

    fn readrom(&self, addr: u16) -> u8 {
        self.mbc.readrom(addr)
    }

    fn writerom(&mut self, addr: u16, value: u8) {
        self.mbc.writerom(addr, value);
    }

    fn writeram(&mut self, addr: u16, value: u8) {
        self.mbc.writeram(addr, value);
    }

    fn is_battery_backed(&self) -> bool {
        self.mbc.is_battery_backed()
    }

    fn loadram(&mut self, ramdata: &[u8]) -> crate::StrResult<()> {
        self.mbc.loadram(ramdata)
    }

    fn dumpram(&self) -> Vec<u8> {
        self.mbc.dumpram()
    }

    fn check_and_reset_ram_updated(&mut self) -> bool {
        self.mbc.check_and_reset_ram_updated()
    }
}

impl Drop for FileBackedMBC {
    fn drop(&mut self) {
        if self.mbc.is_battery_backed() {
            // TODO: error handling
            let mut file = match fs::File::create(&self.rampath) {
                Ok(f) => f,
                Err(..) => return,
            };

            let _ = file.write_all(&self.mbc.dumpram());
        }
    }
}
